Skip to content

release: dev → main (dashboard primitives migration + paper exam UX refresh)#169

Merged
quan0715 merged 60 commits intomainfrom
dev
May 5, 2026
Merged

release: dev → main (dashboard primitives migration + paper exam UX refresh)#169
quan0715 merged 60 commits intomainfrom
dev

Conversation

@quan0715
Copy link
Copy Markdown
Owner

@quan0715 quan0715 commented May 5, 2026

Summary

Release 25 commits from dev to main. The bulk is a multi-step migration of the contest UI to the new dashboard primitive library, plus a focused paper-exam answering UX refresh and one small backend permission tweak.

Highlights

Dashboard primitives (new shared layer)

  • feat(dashboard): typography (PageTitle, SectionTitle, MetricBlock, TimeDisplay), structure (DashboardPage/Container/Block/Header), tabs + toolbar, and split proportions for main+aside layouts.
  • All shipped with stories + tests under frontend/src/shared/components/dashboard/.

Contest UI migration to primitives

  • Student dashboard, header timer & insight rail, hero start/end pairs, admin overview header / exam status, panel headers, command-center tab+toolbar all moved onto the primitives.
  • Countdown unified via CountdownProgress with shimmer; right-rail dividers now flow through DashboardContainer.
  • Polish fixes: announcements use BlockHeader, duplicate answer progress rail removed, participant list header / insight card spacing tidied.

Paper exam answering UX (this batch)

  • Classroom contest routes split: dashboard renders inside MainLayout/new ContestWorkspaceLayout; full ContestLayout shell now reserved for solving/answering.
  • ExamNavigator: collapsible side rail with mini view, optional overview entry, tightened typography + marker tone.
  • PaperExamAnsweringScreen: marked questions now persist in localStorage; submit button + save status forward into the layout header slot via the new ContestLayoutHeaderSlotContext.
  • AnswerDisplay: explicit selected indicator + showCorrectness toggle so the same component handles live answering and post-grading review.
  • PaperQuestionReportCard extracted from ParticipantDashboardPane for reuse across report views.

Backend

  • feat(contest): dashboard-summary endpoint now lets contest participants read it once results_published=true; staff access unchanged. Two regression tests added.

Docs / chore

  • Specs: contest announcement & Q&A, conversation rework (drop status / initiated_by / problem), simpler dashboard.
  • Implementation plan for contest conversation feature.
  • .gitignore: extra db dump/backup formats.
  • Standalone typography token guard script (advisory; not yet in CI).
  • fix(dashboard): refactor tabs story to satisfy react-hooks/rules-of-hooks (pre-push hook surfaced this on first push).

Testing

  • ✅ Frontend tsc --noEmit clean (0 errors)
  • ✅ Pre-push ESLint hook clean after tabs.stories.tsx fix
  • ✅ Backend new tests pass:
    • test_participant_can_view_dashboard_summary_after_results_published
    • test_participant_cannot_view_dashboard_summary_before_results_published
    • test_dashboard_summary_cache_invalidates_on_submission (regression)
  • lint-architecture.js clean
  • ⚠️ lint-naming.js reports only pre-existing categories (not introduced by this batch); no new violations added — these lints are not wired into CI/husky.
  • ⏭️ Frontend integration / e2e tests: not run (out of scope for this prep)

Deployment notes

  • No DB migrations in this batch.
  • Frontend route layout is changed: classroom contest dashboard now renders inside MainLayout. Verify navbar/sidebar rendering on /classrooms/:cid/contest/:cid.
  • New localStorage key prefix qjudge.exam.marked.<contestId> — no migration needed.

Rollout / rollback

  • No feature flag; revert by reverting this merge.
  • Backend permission change is additive (only widens read access to participants after results_published).

quan0715 and others added 28 commits May 5, 2026 11:58
Spec for the Container/Block/Tabs primitive set that replaces the
duplicated title/subtitle/time/divider/tab+toolbar SCSS across the
student contest dashboard and the admin contest dashboard.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Step-by-step plan covering 6 phases: typography, structure, tabs/toolbar,
and migrations of student dashboard / admin overview / contest hero.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PageTitle / SectionTitle / MetricBlock / TimeDisplay built on Carbon
typography tokens. PageTitle uses heading-04 size with weight 400;
others align with the more compact admin scale.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
DashboardPage handles scroll + max-width clamp; DashboardContainer
exposes stack/split/grid layouts and auto-injects dividers between
siblings; DashboardBlock provides padding variants without owning
borders; BlockHeader composes title (page or section size) plus
description and actions slots.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
DashboardTabs context provider coordinates DashboardTabBar (Carbon
Tabs wrapper with toolbar slot and mobile stacking) and
DashboardTabPanel (renders only when its tabId matches the active
context). DashboardToolbar provides Search and Filter sub-components
preset for the borderless in-tab-bar appearance.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the bespoke .root/.dashboard/.layout/.titleRow/.detailRow/.timerValue
SCSS with DashboardPage, DashboardContainer (split + grid + stack),
DashboardBlock, BlockHeader and TimeDisplay/MetricBlock primitives.
Inner Carbon Tabs are kept; only the outer page layout, hero title and
metric cells are migrated.

Also switch BlockHeader description from <p> to <div> so it can host
arbitrary nodes (e.g. tag rows) without nesting block elements in <p>.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
AdminOverviewScreen renderContestHeader now uses DashboardBlock +
BlockHeader; the bespoke .contestHeader / .dashboardTitleBlock /
.dashboardTitleRow / .dashboardDescription SCSS is gone.

AdminOverviewCommandCenter exam status summary now uses two
DashboardContainer grids (people-count and schedule) with MetricBlock
size=lg, plus a stacked DashboardBlock with TimeDisplay countdown for
the timeline progress. The hardcoded 1.5rem font sizes and the
.examStatusMatrix / .examStatusMetric / .examScheduleGrid /
.examScheduleItem / .examProgressTitle / .examProgressValue /
.examStatusPanel / .panelHeader rules are removed.

DashboardPage gains an opt-in fullBleed prop so admin pages can render
without the 1180px clamp.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ContestHero metadata block now composes two MetricBlock instances; the
ad-hoc .timeLabel / .timeValue SCSS rules are removed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
DashboardContainer split layout now accepts proportions="equal" |
"main-aside" | "aside-main". Student dashboard switches to
main-aside so the right summary rail clamps to 18-22rem instead of
sharing 50/50 with the main content. Mobile breakpoint still
collapses to a single column.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…port

Announcement items now compose BlockHeader instead of bespoke
.announcementHeader / .announcementTitle SCSS, so the title typography
matches the rest of the dashboard.

DashboardPage .root and .inner switch to flex-column with the inner
direct child stretched to fill remaining height. With fullBleed, the
bordered split container therefore reaches the bottom of the viewport
when content is short, instead of leaving a dark band underneath.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…d primitives

Six admin panels (AdminPreparationDashboard, OverviewActionWidgets,
OverviewInsightsPanel, OverviewEventSummaryPanel, StudentStatusBreakdown,
DraftChecklistPanel) now use BlockHeader / SectionTitle from
@/shared/components/dashboard for their section titles and
title-with-description-and-actions headers. Their bespoke .title /
.subtitle / .panelHeader / .widgetTitle SCSS rules are removed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- 答題畫面新增唯讀通知 banner + modal,互動回 dashboard
- 已讀狀態用 localStorage(不引入新後端表)
- 教師考試前後皆可發公告;既有 readOnly 限制需解開
- 拆掉 ContestClarifications.tsx 巨檔
- ClarificationViewSet 加 status / problem filter

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… primitives

ContestLayout header timer now uses TimeDisplay variant=header (the
typography rules are removed from the wrapper SCSS).

AdminInsightRail card headers now use MetricBlock size=lg, eliminating
the hardcoded 1.5rem font-size in .cardHeader strong and the bespoke
label/value typography.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…cing

Remove the participant-list description ("依異常程度排序,快速掃描考生狀態。")
since the title is self-explanatory.

Drop the .card min-height: 8rem in AdminInsightRail; the rail cards now
size to their content + padding so the participant-distribution card
doesn't leave empty space below the meter chart.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…bar bell

- 移除 Clarification,改為 ContestConversation + ContestMessage 雙向 thread
- ContestAnnouncement 維持純廣播(取消 target_user 計畫)
- 通知入口改為 ContestLayout navbar 鈴鐺(不再用 banner)
- 教師可從 AdminProctoringPanel 對特定學生主動開啟對話
- 學生可在 thread 中回覆教師
- 已讀仍用 localStorage(key 含 ann: / msg: 前綴)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
兩個欄位都可由訊息推算出來,不必額外存:
- 「進行中/已回覆」看 last_message.sender_role
- 「誰發起的」看第一則 message 的 sender_role
- list endpoint 多回傳 first_sender_role 供前端使用

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…shboard

- ContestConversation 砍掉 problem 欄位
- (contest, student) 加 unique 約束:每個學生在一個 contest 內一條對話
- 學生端 dashboard 直接展開 thread(不需要列表)
- 教師端 filter 簡化為「全部 / 未回 / 已回」
- Proctoring「傳送訊息」走 ensure-and-append(idempotent)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… rail dividers through DashboardContainer

Add a shared CountdownProgress component that owns its own per-second
ticker and renders TimeDisplay + ProgressBar from the dashboard
primitives. Both the student dashboard's primary status block and the
admin overview's exam status panel now use it, so the admin's countdown
ticks live like the student side.

Replace the bespoke .rightOverviewColumn div and AdminInsightRail.root
div with DashboardContainer layout=stack dividers="auto", and drop the
.card border-bottom inside the rail. Dividers between every direct
child in the right rail (examStatusSummary, distribution, grading,
events) are now produced uniformly by the DashboardContainer pattern.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
22 個任務分 6 階段:後端 model+API+測試 → 前端 hooks/utils → 元件 →
通知鈴鐺 → 螢幕接線 → 清理。每步含完整代碼與命令。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ard primitives

The participants/answer-distribution tab strip and its inline search +
filter toolbar now use DashboardTabs / DashboardTabBar /
DashboardTabPanel / DashboardToolbar instead of bespoke .tabRow /
.tabRowToolbar / .tabRowSearch / .tabRowFilterMenu. Mobile stacking,
borderless search input, and toolbar alignment are all owned by the
shared primitives.

DashboardToolbar.Search now accepts id, size, and onClear so existing
callers that need clearable search can opt in without dropping back to
raw Carbon Search.

DashboardTabPanel switches from conditional render to always-render
with hidden=true on inactive panels, matching Carbon Tabs semantics so
screen readers and tests can locate every tabpanel by role.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When the contest is in the during phase, CountdownProgress overlays a
2s linear-gradient shimmer on the progress bar so the bar feels alive
between per-second ticks. Before/after phases stay static.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
scripts/check-typography-tokens.js scans the dashboard primitive
library (src/shared/components/dashboard) for hardcoded font-size
values and exits non-zero if any escape onto a primitive. Carbon CSS
tokens (var(--cds-*)), keywords (inherit/normal/etc.) and SCSS
variables remain allowed. font-weight numbers stay free since not all
weights have semantic Carbon tokens.

Run via `npm run check:typography`. Scope intentionally stops at the
dashboard library so feature-level legacy SCSS isn't disturbed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add *.sql.gz, *.dump, *.backup, and backups/ to .gitignore so local pg_dump artifacts stay out of the working tree.
…ults published

Previously dashboard-summary was teacher/admin only. Now participants of a
contest can read it once `results_published` is true; staff still has
unrestricted access. Adds two regression tests for the new branches.
- Split classroom contest routes: dashboard renders inside MainLayout via
  new ContestWorkspaceLayout; the full ContestLayout shell is now reserved
  for the answering runtime (solve / paper).
- ExamNavigator: collapsible side rail with mini view, optional overview
  entry, refined typography, marker tone, and toolbar slot when the
  surrounding layout exposes its own actions.
- PaperExamAnsweringScreen: persist marked questions in localStorage and
  forward the submit button + save status into the layout header slot via
  ContestLayoutHeaderSlotContext when available.
- AnswerDisplay: gain an explicit selected-answer indicator and a
  showCorrectness toggle so the same component covers both the live
  answering view and the post-grading review.
- Extract PaperQuestionReportCard from ParticipantDashboardPane so the
  per-question grading card can be reused across report views.
Pre-push ESLint flagged the inline `render: () => { ... useState ... }` for
violating react-hooks/rules-of-hooks because the arrow function is not
recognized as a React component. Moves the body into a top-level
`TabsWithToolbarStory` so hooks fire from a proper component.
Copilot AI review requested due to automatic review settings May 5, 2026 07:23
quan0715 and others added 29 commits May 5, 2026 15:46
Student dashboard inner tabs (規則說明 / 作答紀錄) now use BlockHeader
instead of bespoke .sectionHeader / .inlineRecordsHeader /
.inlineRecordsTitle / .sectionDescription markup. The unused
.metricLabel / .metricValue / .chartHeader rules from before the
KPIBlock migration are deleted.

AdminOverviewScreen drops dead .publishSummary / .publishSummaryIntro
/ .publishSummaryGrid / .publishSummaryItem / .publishSummaryLabel /
.publishSummaryValue rules — none of them are referenced anywhere.

AdminOverviewCommandCenter .participantSingleMetric strong replaces
its hardcoded font-size: 1.25rem with the heading-03 Carbon token, so
the participant card emphasis aligns with MetricBlock size=lg.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
11 個任務分 5 階段:偵測 hook + RuntimeRouteWrapper → top nav/UserMenu →
SideMenu idle/runtime → 路由合併 → 清理。每步含完整代碼與命令。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…mitive

The student dashboard's 規則說明 / 作答紀錄 tab strip was still on raw
Carbon Tabs/TabList/TabPanels. Migrate to DashboardTabs +
DashboardTabBar + DashboardTabPanel so the visual treatment and
mobile stacking match the rest of the dashboard primitives.

Drop the .markdownBlock / .feedbackBlock { padding-left: 0.75rem }
indent on PaperQuestionReportCard. The prompt and the question header
now share the same horizontal axis instead of the prompt being
mysteriously inset.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
DashboardTabBar now accepts padding="default" | "flush". The default
adds 1rem inline padding so the tab strip stops hugging the block
edge — matching the inline padding of DashboardBlock padding="default"
and giving the tabs visual breathing room. flush remains available
for callers that need the bar to sit exactly at the container edge.

The toolbar drops its own 0 1rem margin in favour of the tab bar's
padding, keeping the spacing model consistent in both desktop and the
mobile column layout.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The embedded ContestLogsScreen tab strip (違規事件 / 考試事件 + refresh
button) was the last raw Carbon Tabs site in the admin overview. It
now uses DashboardTabs / DashboardTabBar / DashboardTabPanel with the
refresh button passed through the toolbar slot.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The student dashboard's top-level DashboardContainer no longer sets
bordered. Now that the page is rendered fullBleed inside the contest
workspace shell, the outer frame just doubled the chrome and its
bottom edge could be clipped by the scroll container. Internal
dividers between the main column and summary rail are unaffected.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ogress

The right-rail "考生分佈總覽" KPI now reads "學生作答進度" with the
completion percent as its highlighted value (e.g. "36%") instead of
the raw headcount. The breakdown formatter under the meter is
trimmed: it just states "已交卷 X / Y 人" without re-stating the
completion rate that the value already shows.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add openMonitor callback to ContestRuntimeContextValue and wire it to
setMonitoringOpen in RuntimeRouteWrapper so sibling components like
ExamStatusBadge can trigger the monitoring modal via context.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…hooks on every page

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…testProvider dependency

SideMenuContestRuntimeSection no longer calls useContest() (which throws
outside ContestProvider). SideMenu now fetches contest data for all
contest URLs (not just admin) via contestIdToFetch, and passes
problems down as a prop.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The student dashboard 公告 block is gated behind SHOW_ANNOUNCEMENTS,
currently false. Data fetching, state, and renderer are kept intact;
flipping the constant to true brings the block back without revisiting
the call site.

The corresponding integration test is marked as skipped with a note.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…classes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…o preserve precheck handoff

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…obal navbar)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The 管理入口 entry tile grid in AdminPreparationDashboard was a hand-
rolled copy of the Container's bordered grid pattern: a 2-column grid
with 1px outer border, per-cell border-right + border-bottom, and
nth-child / nth-last-child resets plus a mobile single-column
fallback. Replace the wrapper with
<DashboardContainer layout="grid" columns={2} bordered dividers="auto">
and drop the duplicated SCSS — divider behaviour now comes from a
single source.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@quan0715 quan0715 merged commit a24c1ce into main May 5, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants